<?
	// Собственно парсер вставок.
	// Вставка - текст содержащий следующие блоки:
	//  [[tag: p1=`v1` p2=`v2`]] - вставка 
	//  (tag - имя вставки, которая будет вызвана, нечто вроде имени функции,
	//	p1 - имя первого параметра, v1 - значение первого параметра и т.д.)
	// {{var}} - переменная (var - имя переменной).
	//  <? ? > простой код на php.
	// Пример:
	// код вставки ins1: $res .= '('.$x.')';
	// [[ins1: x=`100`]] выведет (100).
	// Переменные хранятся в $this->vars.
	class c_insertion_util
	{
		var $debug = false;
		var $subCode = 0; // Счетчик для вставок.
		var $subs = array(); // Массив со вставками.
		var $level = 0; // Уровень вложенного вызова. Используется при рекурсии.
		var $insertions = array(); // Все вставки. Для ускорения работы.
		var $allChunks = array(); // Все чанки MODX. Для ускорения работы.
		var $report = array(); // Отчет о количестве вызовов функций.
		var $funcId = 0; // ID функции для вызова (смотри функцию prepare).
		var $active = true; // Парсер активен. Все найденные спецблоки обрабатываются. Если выключен, то блоки остаются без изменения.
		var $max_levels = 64; // Максимальное количество уровней прохода парсера.
		
		var $vars = array();
		
		var $config = array(
			'ins_brackets' => array('<<', '>>'),
			'tpl_brackets' => array('<{', '}>'),
			'var_brackets' => array('{{', '}}'), // Переменная которая будет заменена на месте.
			'sub_brackets' => array('<*%', '%*>'), // В таких скобках находится имя выделенной вставки для массива $subs.
			'table' => 'insertions',
			'leave_unknown' => false
		);
		
		// Коструктор класса.
		function c_insertion_util()
		{
			global $mvc;

			// Ссылка на параметры запроса.
			$this->vars['$R'] = &$_REQUEST;
			// Ссылка на параметры сессии.
			$this->vars['$S'] = &$_SESSION;
			
			// Загрузка всех вставок, чтобы сократить количество обращений к базе.
			$sql = $mvc->parse('SELECT name, code FROM {dbpref}insertions');
			$r = $mvc->db->query($sql);
			while ($a = mysql_fetch_assoc($r))
			{
				$this->insertions[$a['name']] = $a['code'];
			}
			mysql_free_result($r);
		}		
		
		function message($mes, &$text)
		{
			if ($this->debug)
			{
				global $mvc;
				$mvc->message(str_repeat('+', $this->level).' <b style="border:1px solid #ccc">'.$mes.'</b> '.htmlspecialchars($text));
			}
		}
		
		// Подготовка и обработка текста.----------------------------------------
		// Priavte. Подготовить текст к обработке разобрать вложенные вставки.
		function prepare(&$text)
		{
			global $mvc;
		
			$this->message('will prepare', $text);
			
			$this->report['prepare']++;

			// Замена переменных на месте.
			$tpl = '/'.$this->config['var_brackets'][0].'([^{^}]*)'.$this->config['var_brackets'][1].'/';
			while (preg_match($tpl, $text, $res))
			{
				$text = str_replace($res[0], $this->getVar($res[1]), $text);
			}			
			
			// Поиск и выделение блоков с php кодом.			
			while (($ind_r = strpos($text, '?'.'>')) !== false)
			{
				$ind_l = $ind_r;
				// Поиск открывающих скобок.
				while(($ind_l > 0) && (($text[$ind_l + 1] != '?') || ($text[$ind_l] != '<'))) $ind_l--;
								
				$key = 'php'.$this->subCode++;
				$this->subs[$key] = substr($text, $ind_l, $ind_r - $ind_l);
				$text = substr($text, 0, $ind_l).
					$this->config['sub_brackets'][0] .$key. $this->config['sub_brackets'][1].
					substr($text, $ind_r + 2); 
			}			
			
			$ind_r = 0;
			// Поиск закрывающих скобок.
			
			$asd = 5;

			// Поиск вставок и замена их метками.
			if ($asd == 0)
			{
				// Разворачиваем параметр, так как искать будем в перевернутой строке..
				$rib0 = strrev($this->config['ins_brackets'][0]);
				// Ищем первое вхождение закрывающей скобки.
				while (($ind_r = strpos($text, $this->config['ins_brackets'][1], $ind_r)) !== false)
				{
					// Разворачиваем строку,
					$rtext = strrev($text);
					// и ищем первое вхождение открывающей скобки с позиции последней найденной открывающей.
					// Затем преобразуеминдекс в прямой.
					$ind_l = strlen($text) - strpos($rtext, $rib0, strlen($text) - $ind_r) - strlen($rib0);
					
					// Если открывающая скобка была найдена, то обработаем ее.
					if ($ind_l >= 0)
					{
						$key = 'sub'.$this->subCode++;
						$this->subs[$key] = substr($text, $ind_l + 2, $ind_r - $ind_l - 2);
						$text = substr($text, 0, $ind_l).
							$this->config['sub_brackets'][0] .$key. $this->config['sub_brackets'][1].substr($text, $ind_r + 2);
						$ind_r = $ind_l;
					}
					else
					{
						// Иначе ругнемся и выйдем.
						die('<font color="red">Open bracket not found</font>');
					}
				}
			}			
			
			
			elseif ($asd == 5)
			{				
				$offset = 0;
				while ($chunk = $this->find_chunk($text, $this->config['ins_brackets'], $offset))
				{					
					$key = 'sub'.$this->subCode++;
					$this->subs[$key] = substr($text, $chunk[2], $chunk[3]);
					$text = substr($text, 0, $chunk[0]).
						$this->config['sub_brackets'][0] .$key. $this->config['sub_brackets'][1].
						substr($text, $chunk[0] + $chunk[1]);
						
					$offset = $chunk[0];
				}
			}			
			
			// Иные способы поиска.
			
			elseif ($asd == 1)
			{
				while (($ind_r = strpos($text, ']]', $ind_r)) !== false)
				{
					$ind_l = $ind_r;
					// Поиск открывающих скобок.
					while($ind_l > 0)
					{
						if ( $text[$ind_l + 1] == '[')
						{
							if ($text[$ind_l] == '[')
							{
								break;
							}
							elseif ( ($text[$ind_l] == '!') && (substr($text, $ind_l, 8) == '![CDATA['))
							{
								break;
							}
						}
						$ind_l--;
					}
					
					if ($ind_l >= 0)
					{
						$key = 'sub'.$this->subCode++;
						$this->subs[$key] = substr($text, $ind_l + 2, $ind_r - $ind_l - 2);
						$text = substr($text, 0, $ind_l).
							$this->config['sub_brackets'][0] .$key. $this->config['sub_brackets'][1].substr($text, $ind_r + 2);
						$ind_r = $ind_l;
					}
					else
					{
						die('<font color="red">Open bracket not found</font>');
					}
				}
			}
			elseif ($asd == 2)
			{
				$tpl = '/(?:\[\[([^\[{2}]*?)\]\]/is';
				//while (preg_match($tpl, $text, $res))
				if (preg_match_all($tpl, $text, $reses))
				{
					foreach($reses[0] as $res)
					{
						$key = 'sub'.$this->subCode++;
						$this->subs[$key] = $res[1];//substr($text, $ind_l + 2, $ind_r - $ind_l - 2);
						$sub = $this->config['sub_brackets'][0].$key.$this->config['sub_brackets'][1];
						$text = str_replace($res, $sub, $text);
						
					}
					
					/*
					$key = 'sub'.$this->subCode++;
					$this->subs[$key] = $res[1];//substr($text, $ind_l + 2, $ind_r - $ind_l - 2);
					$sub = $this->config['sub_brackets'][0].$key.$this->config['sub_brackets'][1];
					$text = str_replace($res[0], $sub, $text);
					*/
					
					//$text = substr($text, 0, $ind_l).
					//	$this->config['sub_brackets'][0].$key.$this->config['sub_brackets'][1].
					//	substr($text, $ind_r + 2);
					//$ind_r = $ind_l;
	
				}			
			}
			
			/*
			$offset = 0;
			while ($chunk = $this->find_chunk($text, $this->config['tpl_brackets'], $offset))
			{
				// Если открывающая скобка была найдена, то обработаем ее.
				$key = 'var'.$this->subCode++;
				$this->subs[$key] = substr($text, $chunk[2], $chunk[3]);
				$text = substr($text, 0, $chunk[0]).
					$this->config['sub_brackets'][0] .$key. $this->config['sub_brackets'][1].
					substr($text, $chunk[0] + $chunk[1]);

				$offset = $chunk[0];
			}			
			*/
			
			$this->message('after prepare', $text);

			return $text;
		}
		
		// Получить переменную с именем name. Также ищет переменную '$'.name.
		function getVar($name, $default = NULL)
		{
			$this->message('getvar', $name);						
			$this->report['getVar']++;
			
			// Перемнная имеет вид имя;умолчание;тип.
			$name = trim($name);
			$varParts = split(';', $name);
			
			// Поиск переменной.
			//$parts = split('\.', trim($varParts[0]));
			$parts = explode('.', trim($varParts[0]));
			//$var = NULL;
			
			$var = $this->vars;

			for($i = 0; $i < sizeof($parts); $i++)
			{
				$found = false;
				
				// Это объект. Ищем его поле.
				if (is_object($var))
				{
					$obj_vars = get_object_vars($var);
					if (array_key_exists($parts[$i], $obj_vars))
					{
						$var = $obj_vars[$parts[$i]];					
						$found = true;
					}
				}
				// Это массив. Ищем значение в нем.
				elseif (is_array($var))
				{
					if (array_key_exists($parts[$i], $var))
					{
						$var = $var[$parts[$i]];
						$found = true;
					}
															
					elseif (array_key_exists('$'.$parts[$i], $var))
					{
						$var = $var['$'.$parts[$i]];
						$found = true;
					}
				}
					
				if (!$found)
				{
					$var = (sizeof($varParts) > 1) ? $varParts[1] : ''; // Поле не найдено взять умолчание из переменной;
					break;
				}
			}
			
			$this->message('var is', $var);

			// Умолчание переменной не определено, взять умолчание из параметра.
			if ($var === NULL) $var = $default;
			
			$type = sizeof($varParts) > 2 ? $varParts[2] : ''; // Выделим тип переменной.
			switch ($type)
			{
				case 'int': $var = (int)$var; break;
				case 'float': $var = (float)$var; break;
				case 'bool': $var = (bool)$var; break;
				case 'string': $var = mysql_escape_string($var); break;
			}
			return $var;
		}
		
		// Сокращение getVar для удобства.
		function v($name)
		{
			return $this->getVar($name);
		}
		
		// Установить значение переменной.
		function set_var($name, $value)
		{
			$this->vars[$name] = $value;
		}
		
		// Ищет в строке чанк - блок от $brackets[0] до $brackets[1] и возвращаетв в массиве начало[0], длину блока[1],
		// начало тела[2], длину тела[3]. Тело - блок без скобок.
		function find_chunk(&$text, $brackets, $offset = 0)
		{
			// Ищем первое вхождение закрывающей скобки.
			if (($ind_r = strpos($text, $brackets[1], $offset)) !== false)
			{
				$b0_len = strlen($brackets[0]) - 1;
				for($i = $ind_r - 1; $i > 0; $i--)
				{
					if ($text[$i] == $brackets[0][$b0_len])
					{						
						$ok = true;
						for($j = 1; $j <= $b0_len; $j++)
						{							
							if ($text[$i - $j] != $brackets[0][$b0_len - $j])
							{
								
								$ok = false;
								break;
							}
						}

						if ($ok)
						{
							$i++;
							$b0_len = strlen($brackets[0]);
							$b1_len = strlen($brackets[1]);
							return array($i - $b0_len, $ind_r - $i + $b0_len + $b1_len, $i, $ind_r - $i);
						}						
					}
				}
			}			
			
			return 0;
		}
		
		// Private. Вернуть результат обработки подготовленного текста.
		function parse_prepared(&$text)
		{
			$this->message('parse_prepared', $text);
			
			$this->report['parse_prepared']++;
		
			$hasChanges = false;
			
			// Список искомых тегов.
			$tags = $this->config['sub_brackets'];
			
			// Поиск специальных блоков (вставок).
			while ( ($ind_l = strpos($text, $tags[0])) !== false ) 
			{
				$this->report['parse_prepared_inner']++;
				$ind_r = strpos($text, $tags[1], $ind_l);

				// Если тег найден, то обрабатываем его содержимое.
				//if ($tagIndex != -1)
				//{
					/*
						// Ищем закрывающий тег. И заменяем внутренности.						
						$ind_l += strlen($tags[$tagIndex][0]);						
						$sub = substr($text, $ind_l, $ind_r - $ind_l); // Текст блока.
						$prefix = substr($text, 0, $ind_l - strlen($tags[$tagIndex][0])); // Текст до блока.
						$postfix = substr($text, $ind_r + strlen($tags[$tagIndex][1])); // Текст за блоком.
					*/
				// Ищем закрывающий тег. И заменяем внутренности.
				$ind_l += strlen($tags[0]);
				$sub = substr($text, $ind_l, $ind_r - $ind_l); // Текст блока.
				$prefix = substr($text, 0, $ind_l - strlen($tags[0])); // Текст до блока.
				$postfix = substr($text, $ind_r + strlen($tags[1])); // Текст за блоком.
				
				// Вставка.
				$this->message('found insertion', $sub);
				$this->report['parse_prepared_sub']++;
				$last_sub = $sub;
				
				//if ($this->level++ > 64) die('Too deep parsing. More than 64 level reached.');
				$sub = $this->callsub($sub);
				//$this->level--;

				// Обрабатываем найденный текст.
				//$sub = $this->parse($sub);
				$this->message('after replace', $sub);
				// Перепроверим только полученный текст.
				if ($sub != $last_sub) $sub = $this->prepare($sub);					
				$text = $prefix.$sub.$postfix;
			}

			$this->message('end parse_prepared', $text);
			return $text;
		}		
		
		// Private. Обработать вставку именуемую после подготовки(prepare) $subName.
		function callsub($subName)
		{
			$this->report['callsub']++;		
			$this->message('callsub', $subName);

			$subText = $this->subs[$subName];			
			
			// Вставка есть код на php, вернем результат.
			if (substr($subText, 0, 2) == '<?')
			{
				$this->report['callsub_php']++;
				$subText = substr($subText, 2);

				$this->message('thats PHP', $subText);
				
				extract($this->vars, EXTR_SKIP);
				ob_start();				
				//$res = eval('$modx = &$this->modx; '.$subText.';');
				$res = eval('global $mvc; '.$subText.';');
				$mes = ob_get_contents();
				ob_end_clean();				
				
				return $mes.$res;
			}
			
			// Вставка не есть код на PHP.
			$this->level++;
									
			// Заполнение параметров вызова $this->vars.
			$name = $subText;
			$vars = $this->vars;
			$this->vars = array('parent' => &$vars);
			
			$ind = strpos($subText, ':');
						
			// Подготовка параметров.
			if ($ind != false)
			{
				$name = substr($subText, 0, $ind);
				$parts = split('`', substr($subText, $ind + 1));
				for($i = 1; $i < sizeof($parts); $i += 2)
				{
					$paramName = trim(str_replace('=', '', $parts[$i - 1]));
					// Преобразуем переменные шаблона в вид простых переменных.
					$this->vars[$paramName] = str_replace($this->config['tpl_brackets'], $this->config['var_brackets'], $parts[$i]);
				}
			}
			$this->vars['_name'] = $name;

			// Вызов необходимой вставки с загруженными параметрами.
			$result = $this->call($name);

			// Вернуть стек с переменными.
			$this->vars = &$this->vars['parent'];
			
			$this->level--;
			
			return $result;
		}
				
		// Public. Обработать вставку с именем $name. Текст берется из базы
		// или из ./insertions/($name).php. Если такой вставки не найдено, то вернется NULL.
		function call($name)
		{
			$this->report['call']++;

			$this->message('call', $name);

			//$name = $this->parse($name);
			// Получаем код вставки.
			$code = $this->getCode($name);

			$this->message('call code', $name);

			// Если такая вставка обнаружена, то выполним ее.
			if ($code != NULL)
			{
				ob_start();
				$res = '';
				if (is_array($code))
				{
					if ($code[1]) // Метод объекта (объект, метод).
						$res = call_user_method($code[1], $code[0], $this->vars, &$this);
					else // Это просто функция (имя функции).
						$res = call_user_func($code[0], $this->vars, $this);
				}
				else
				{
					$this->message('call code NOT NULL', $code);
					$res = $this->parse($code);
				}

				return ob_get_clean().$res;
			}
			else
			{
				$this->message('call code NULL call controller', $code);
				
				global $mvc;
				//list($name, $func) = explode('.', $name);
				//$ans = $mvc->run_controller($name, $func, $this->vars);
				//$this->message('controller says', $ans);
				return 'CONTROLER';//$ans;
				
				//$mvc->load_controller($name);
				//$mvc->run_controller($name, $func, $args);
				//return NULL;
			}
		}		

		//Public. Получить код вставки с именем $name. Ограничение на размер файла 16Kb.
		function getCode($name)
		{
			$this->report['getCode']++;
		
			// Новый вариант вызова с предварительной загрузкой.
			$name = trim($name);
			return $this->insertions[trim($name)];
		}
		
		// Установить код вставки (на 1 сессию).
		function setIns($name, $code)
		{
			$this->insertions[$name] = $code;
		}
		
		// Public. Вернуть результат обработки текста. Главная функция класса.
		function parse($text)
		{
			//if (!$this->active) return $text;
			global $mvc;
			
			$this->message('start parse', $text);

			$this->report['parse']++;
		
			if ($text == NULL) return NULL; // Текста нет, вернем null.
			
			// Текст не содержит спецсимволов. Это просто текст.
			if ( !strpos($text, $this->config['ins_brackets'][1])
				&& !strpos($text, $this->config['tpl_brackets'][1])
				&& !strpos($text, $this->config['var_brackets'][1])
				&& !strpos($text, $this->config['sub_brackets'][1])
				&& !strpos($text, '?>') )
			{				
				return $text;
			}
			

			//$this->report['parse_notnull']++;
				
			//if ($this->level++ > 64) die('Too deep parsing. More than 64 level reached.');
			//if ($this->level++ > $this->max_levels) return $text;
			
			$res = $this->prepare($text);
			
			// Парсим только если в тексте есть вставки.
			if (strpos($res, $this->config['sub_brackets'][1]))
				$res = $this->parse_prepared($res);

			//$this->level--;

			$this->message('end parse', $res);
			
			return $res;
		}
		
		// Возвращает код со всеми вставками замененными на php код.
		function compile($text, $vars = array())
		{
			echo 'COMPILE: '.htmlspecialchars($text).'<hr>';
			$text = $this->prepare_compile($text, $vars);
			
			echo 'AFTER PRECOMPILE: '.htmlspecialchars($text).'<hr>';
			echo 'SUBS: '.htmlspecialchars(print_r($this->subs, true)).'<hr>';
			
			$text = $this->copile_precompiled($text);
			
			//$text = str_replace('? >< ?', '', $text);
			
			//return $text;
			/*
			$res = '';
			while (($ind = strpos($text, '<?')) !== false)
			{			
				echo '<br><b>'.$ind.'</b><br>';
				$len = strpos($text, '?>') - $ind;
				$prefix = mysql_escape_string(substr($text, 0, $ind));
				//$prefix = substr($text, 0, $ind);
				if ($prefix)
					$res .= 'echo (\''.$prefix."');\n";
				$res .= trim(substr($text, $ind + 2, $len - 2))."\n";
				$text = substr($text, $ind + $len + 2);
			}
			if ($text)
				$text = $res.'echo(\''. mysql_escape_string($text)."');\n";
				//$text = $res.'echo(\''. $text."');\n";
			else
				$text = $res;
				
			*/
				
			//$text = str_replace('<?', "<?", $text);
			
			return $text;
		}
		
		function getVar_compiled($name)
		{
			return $name;
		
			$this->message('getvar_compiled', $name);
			
			$this->report['getVar']++;
			
			// Перемнная имеет вид имя;умолчание;тип.
			$name = trim($name);
			$varParts = split(';', $name);
			
			// Поиск переменной.
			$parts = explode('.', trim($varParts[0]));
			
			return '$this->vars[\''.implode('\'][\'', $parts).'\']';
			
			// ================================================
			
			
			
			
			
			
			$var = $this->vars;
			$svar = '$this->vars';

			for($i = 0; $i < sizeof($parts); $i++)
			{
				$found = false;
				
				// Это объект. Ищем его поле.
				if (is_object($var))
				{
					$obj_vars = get_object_vars($var);
					if (array_key_exists($parts[$i], $obj_vars))
					{
						$var = $obj_vars[$parts[$i]];
						//TODO
						$found = true;
					}
				}
				// Это массив. Ищем значение в нем.
				elseif (is_array($var))
				{
					if (array_key_exists($parts[$i], $var))
					{
						$var = $var[$parts[$i]];
						$svar .= '[\''.$parts[$i].'\']';
						$found = true;
					}
															
					elseif (array_key_exists('$'.$parts[$i], $var))
					{
						$var = $var['$'.$parts[$i]];
						$found = true;
					}
				}
					
				if (!$found)
				{
					$var = (sizeof($varParts) > 1) ? $varParts[1] : ''; // Поле не найдено взять умолчание из переменной;
					break;
				}
			}
			
			$this->message('var is', $var);

			// Умолчание переменной не определено, взять умолчание из параметра.
			if ($var === NULL) $var = $default;
			
			$type = sizeof($varParts) > 2 ? $varParts[2] : ''; // Выделим тип переменной.
			switch ($type)
			{
				case 'int': $var = (int)$var; break;
				case 'float': $var = (float)$var; break;
				case 'bool': $var = (bool)$var; break;
				case 'string': $var = mysql_escape_string($var); break;
			}
			return $svar;
		
		}
		
		function prepare_compile($text, $vars = array())
		{
			global $mvc;
		
			$this->message('PREPARE_COMPILE', $text);

			echo 'PREPARE_COMPILE: '.htmlspecialchars($text).'<hr>';
			
			$this->report['prepare']++;

			// Поиск и выделение блоков с php кодом.			
			while (($ind_r = strpos($text, '?'.'>')) !== false)
			{
				$ind_l = $ind_r;
				// Поиск открывающих скобок.
				while(($ind_l > 0) && (($text[$ind_l + 1] != '?') || ($text[$ind_l] != '<'))) $ind_l--;
								
				$key = 'php'.$this->subCode++;
				$this->subs[$key] = trim(substr($text, $ind_l + 2, $ind_r - $ind_l - 2));
				$text = substr($text, 0, $ind_l).
					$this->config['sub_brackets'][0].$key.$this->config['sub_brackets'][1].
					substr($text, $ind_r + 2); 
			}

			// Замена переменных на месте.
			$tpl = '/'.$this->config['var_brackets'][0].'([^{^}]*)'.$this->config['var_brackets'][1].'/';
			while (preg_match($tpl, $text, $res))
			{
				if ($res[1] == '$tpl')
				{
					//echo $res[1];
					//my_print_r($vars);
				}
				
				if (array_key_exists($res[1], $this->vars))
					//$text = str_replace($res[0], "<? echo(".$this->vars[$res[1]].");?".">", $text);
					$text = str_replace($res[0], $this->vars[$res[1]], $text);
				else
					$text = str_replace($res[0], "<? echo(".$this->getVar_compiled($res[1]).");?>", $text);
			}
			
			
			{				
				$offset = 0;
				while ($chunk = $this->find_chunk($text, $this->config['ins_brackets'], $offset))
				{					
					$key = 'sub'.$this->subCode++;
					$this->subs[$key] = substr($text, $chunk[2], $chunk[3]);
					$text = substr($text, 0, $chunk[0]).
						$this->config['sub_brackets'][0] .$key. $this->config['sub_brackets'][1].
						substr($text, $chunk[0] + $chunk[1]);
					$offset = $chunk[0];
				}
			}
		
			$this->message('after prepare', $text);

			return $text;
		}
		
		function copile_precompiled($text)
		{
			$this->message('parse_prepared', $text);
			
			$this->report['parse_prepared']++;
			
			echo 'COPILE_PRECOMPILED: '.htmlspecialchars($text).'<hr>';
			
			// Список искомых тегов.
			$tags = $this->config['sub_brackets'];
			
			// Поиск специальных блоков (вставок).
			while ( ($ind_l = strpos($text, $tags[0])) !== false ) 
			{
				$this->report['parse_prepared_inner']++;
				$ind_r = strpos($text, $tags[1], $ind_l);

				// Ищем закрывающий тег. И заменяем внутренности.
				$ind_l += strlen($tags[0]);
				$sub = substr($text, $ind_l, $ind_r - $ind_l); // Текст блока.
				//die($block);
				$prefix = substr($text, 0, $ind_l - strlen($tags[0])); // Текст до блока.
				$postfix = substr($text, $ind_r + strlen($tags[1])); // Текст за блоком.
				
				// Вставка.
				$this->message('found insertion', $sub);
				
				echo 'COPILE_PRECOMPILED _SUB: '.htmlspecialchars($sub).'<hr>';
				
				$this->report['parse_prepared_sub']++;
				//$last_sub = $sub;				

				//echo '<hr color="green">';
				//my_print_r($text);
				//my_print_r($sub);
				
				$sub = $this->callsub_compile($sub);// $this->callsub($sub);
				
				//echo '<hr color="red">';
				//my_print_r($sub);

				// Обрабатываем найденный текст.
				//$sub = $this->parse($sub);
				$this->message('after replace', $sub);
				// Перепроверим только полученный текст.
				//TODO:if ($sub != $last_sub) $sub = $this->prepare($sub);					
				$text = $prefix.$sub.$postfix;
				echo '*'.$text.'*<br>';
			}

			$this->message('end parse_prepared', $text);
			return $text;		
		}
		
		function callsub_compile($subName)
		{
			$this->report['callsub']++;		
			$this->message('callsub', $subName);

			echo 'CALLSUB_COMPILE: '.htmlspecialchars($subName).'<hr>';

			$subText = $this->subs[$subName];
			
			echo 'CALLSUB_COMPILE _TEXT: '.htmlspecialchars($subText).'<hr>';
			
			//echo 'in callsub_compile: '.$subName.'<br>';

			// Вставка есть код на php, вернем результат.
			if (substr($subName, 0, 3) == 'php')
			{				
				echo 'found php:'.$subText.'<hr>';
				
				// Замена переменных на месте.
				$tpl = '/'.$this->config['var_brackets'][0].'([^{^}]*)'.$this->config['var_brackets'][1].'/';
				while (preg_match($tpl, $subText, $res))
				{
					//print_r($this->vars[$res[1]]);
					//echo 'SUBTEXT:'.$res[1];
					//$subText = str_replace($res[0], $this->getVar_compiled($res[1]), $subText);
					$subText = str_replace($res[0], $this->vars[$res[1]], $subText);
				}			
				echo 'PARSED PHP: '.htmlspecialchars($subText).'<hr>';
				return '<? '.$subText.' ?>'; // changed;	
			}			
			// Вставка не есть код на PHP.
						
			// Заполнение параметров вызова $this->vars.
			$name = $subText;
			if (!isset($this->vars))
				$this->vars = array();
			else
			{
				$tmp = $this->vars;
				$vars = array('_parent' => &$tmp);
				foreach($this->vars as $k=>$v)
				{
					if (substr($k, 0, 1) == '$')
						$vars[$k] = $v;
				}
			}
			$this->vars = $vars;
			
			$ind = strpos($subText, ':');
			
			//$indMody = strpos($subText, ':');
			//$indModx = strpos($subText, '?');
			
			//if ($indModx == false) $indModx = strlen($subText);
			//if ($indMody == false) $indMody = strlen($subText);
			
			//$ind = ($indModx != $indMody) ? min($indModx, $indMody) : false;
			
			// Подготовка параметров.
			$vars2 = array();
			$svars = '';
			if ($ind != false) // Сниппет в формате modx или mody.
			{
				$name = substr($subText, 0, $ind);
				$parts = split('`', substr($subText, $ind + 1));
				for($i = 1; $i < sizeof($parts); $i += 2)
				{
					$paramName = trim(str_replace('=', '', $parts[$i - 1])); // TODO: возможно нужно через parse.
					$paramName = trim($paramName, '&');

					//mysql_escape_string($this->compile(str_replace($this->config['tpl_brackets'], $this->config['var_brackets'], $parts[$i]))).'\'; ';
					echo 'CALLSUB_COMPILE _PARSEVAR: '.htmlspecialchars($parts[$i]).'<hr>';
					
					$this->vars[$paramName] = $this->compile(str_replace($this->config['tpl_brackets'], $this->config['var_brackets'], $parts[$i]));
					echo 'CALLSUB_COMPILE _PARSEDVAR: '.htmlspecialchars($this->vars[$paramName]).'<hr>';
					
					// $this->vars[$paramName] = '<?'.$this->vars[$paramName].'?'.'>';
					//my_print_r($parts[$i]);
					//my_print_r($this->vars[$paramName]);
					//die($vars[$paramName]);
					//$svars .= '$'.$paramName.'=\'? >'.mysql_escape_string($this->compile(str_replace($this->config['tpl_brackets'], $this->config['var_brackets'], $parts[$i]))).'\'; ';

					// Преобразуем переменные шаблона в вид простых переменных.
					//$this->vars[$paramName] = str_replace($this->config['tpl_brackets'], $this->config['var_brackets'], $parts[$i]);
				}
			}
			$this->vars['_name'] = $name;

			//my_print_r($this->insertions[$name]).'<hr>';

			//die($this->compile($this->insertions[$name], $vars2));
			$result = //'<? '.
			//'$this->vars = array(\'_parent\' => &$this->vars); '.			
			$this->compile($this->insertions[$name], $vars2).
			'';//'$this->vars = &$this->vars[\'_parent\'];'.
			//' ?'.'>';
			
			//return ('<? '.$svars.' '.ltrim($this->insertions[$name], '<?'));

			// Вызов необходимой вставки с загруженными параметрами.
			//$result = $this->call($name);

			// Вернуть стек с переменными.
			$this->vars = &$this->vars['_parent'];
			return $result;
		}
		
		function run($text)
		{
			ob_start();
			eval('?>'.$text);
			return ob_get_clean();
		}
	}
?>